// PCB-clock
#include <TimeLib.h>  //增加了getSysTime函数
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <EEPROM.h>
#include <DS18B20.h>
#include <RtcDS1302.h>
#include "WifiConfig.h"
#include "driver_tm1637.h"
#include "WebInterface.h"


#define TEMP_PIN D5
#define PIN_BELL 15

#define BELL_ON digitalWrite(PIN_BELL, HIGH)
#define BELL_OFF digitalWrite(PIN_BELL, LOW)

#define SYS_TIME_SYNC_INTERVAL     120   // 更新系统时间的间隔，单位秒
#define NET_TIME_SYNC_INTERVAL     360   // 读取一次网络时间，间隔【SYS_TIME_SYNC_INTERVAL * NET_TIME_SYNC_INTERVAL】秒

// 函数声明
time_t getNtpTime();
void digitalClockDisplay();
void sendNTPpacket(IPAddress& address);

// 全局变量声明
extern S_ConfClock s_conf_clock;

// NTP Servers
static const char* ntpServerName[] = { "ntp.ntsc.ac.cn", "time.nist.gov", "time.windows.com" };
const int timeZone = 8;         // Beijing Standard Time
unsigned int localPort = 2390;  // local port to listen for UDP packets
unsigned char DOT_GPIO;
unsigned int SECOND_DOT_GPIO;
char InitTimeFlag = 1;
bool use_ds1302 = true;
bool wifi_connected = false;
bool IsSetupStatus = true;
bool update_ds1302 = false;

// 实例化对象
TM1637 DigitLed(CLK, DIO);
DS18B20 ds(TEMP_PIN);
WiFiUDP Udp;
ThreeWire Ds1302Wire(13, 12, 14);  // IO, SCLK, CE
RtcDS1302<ThreeWire> Rtc(Ds1302Wire);

void setup() {
  Serial.begin(BAUD_RATE);
  EEPROM.begin(64);

  Rtc.Begin();
  Rtc.SetIsRunning(true);
  if (!Rtc.GetIsRunning()) {
    use_ds1302 = false;
    Serial.println("Failed to read the DS1302");
  }

  pinMode(PIN_BELL, OUTPUT);
  pinMode(DOT, OUTPUT);              //配置LED口为输出口
  pinMode(DOT2, OUTPUT);             //配置LED口为输出口
  pinMode(PIN_LED, OUTPUT);          //配置LED口为输出口
  pinMode(PIN_RESET, INPUT_PULLUP);  //按键上拉输入模式(默认高电平输入,按下时下拉接到低电平)
  InitSysConfig();
  DOT_GPIO = s_conf_clock.usb_position == e_usb_position_left ? DOT : DOT2;

  digitalWrite(PIN_LED, LOW);  //初始灯灭
  WiFi.hostname(HOST_NAME);    //设置设备名
  WiFi.persistent(true);       // 保存wifi配置

  digitalWrite(PIN_BELL, LOW);
  connectToWiFi(CONN_TIMEOUT);

  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());

  setSyncProvider(getNtpTime);  // 初始校准时间，配置回调函数，
  setSyncInterval(SYS_TIME_SYNC_INTERVAL);          // 间隔校准
  IsSetupStatus = false;
}

time_t prevDisplay = 0;  // when the digital clock was displayed

void loop() {
  //长按5秒(P0)清除网络配置信息，短按显示配置ip
  if (!digitalRead(PIN_RESET)) {
    IPAddress ip = WiFi.localIP();
    digitalWrite(DOT, HIGH);
    digitalWrite(DOT2, HIGH);
    for (int i = 0; i < 4; i++){
      digitalWrite(DOT_GPIO, HIGH);
      delay(200);
      digitalWrite(DOT_GPIO, LOW);
      DigitLed.DisplayNum(2, ip[i]);
      delay(900);
    }

    update_ds1302 = true;
    digitalWrite(DOT_GPIO, HIGH);
    
    if (!digitalRead(PIN_RESET)) {
      //      Serial.println("\n按键已长按4秒,正在清空网络连保存接信息.");
      restoreWiFi();  //删除保存的wifi信息
      IsSetupStatus = true;
      Serial.println("reboot...");
    }
  }

  if (checkWifiConnect()) {
    update_ds1302 = true;
    setSyncInterval(3);
  }
  checkRequest();

  if (timeStatus() != timeNotSet) {
    time_t now_timestamp = now();
    if (now_timestamp != prevDisplay) {  //update the display only if time has changed
      prevDisplay = now_timestamp;
      digitalClockDisplay();
    }
  }
}

  // 蜂鸣器设置
void bell(int h, int m, int s) {
  switch (s_conf_clock.chimehour) {
    case e_ChimeHour_HalfHour:
      {
        if ((m % 30) == 0 && s == 0)
          BELL_ON;
        break;
      }

    case e_ChimeHour_AnHour:
      {
        if (m == 0 && s == 0)
          BELL_ON;
        break;
      }

    case e_ChimeHour_12Hour:
      {
        if (h == 12 && m == 0 && s == 0)
          BELL_ON;
        break;
      }

    default:
      {
        BELL_OFF;
        break;
      }
  }
}

// 亮度检测和设置
void brightnessControl(void) {
  if (s_conf_clock.auto_brightness) {
    int v = analogRead(A0);
    if (v > 1000) {
      DigitLed.SetBrightness(7);
      s_conf_clock.brightness = 7;
    } else if (v > 400) {
      DigitLed.SetBrightness(2);
      s_conf_clock.brightness = 2;
    } else {
      DigitLed.SetBrightness(0);
      s_conf_clock.brightness = 0;
    }
  } else {
    DigitLed.SetBrightness(s_conf_clock.brightness);
  }
}

  // usb线位置，切换显示方向
void usbPosition(void) {
  if (s_conf_clock.usb_position == e_usb_position_left) {
    DigitLed.SetFlip(false);
    digitalWrite(DOT2, LOW);
    DOT_GPIO = DOT;
    SECOND_DOT_GPIO = DOT2;
  } else {
    DigitLed.SetFlip(true);
    digitalWrite(DOT, LOW);
    DOT_GPIO = DOT2;
    SECOND_DOT_GPIO = DOT;
  }
}

void displayDate(int mon, int d) {
  // 显示日期: 12-25
  digitalWrite(SECOND_DOT_GPIO, HIGH);
  digitalWrite(DOT_GPIO, HIGH);
  DigitLed.DisplayDash4Num(0, mon*100 + d);
}

void displayTime(int mon, int d, int h, int m, int s) {
  int tmp;
  unsigned int cur_time = 0;
  static unsigned int second_count = 0;
  static bool display_date = false;

  do {
    // 需要显示日期
    if (s_conf_clock.date_interval > 0) {
      second_count++;
      if (second_count > 1 && !display_date) {
        second_count = 0;
        display_date = true;
        digitalWrite(SECOND_DOT_GPIO, LOW);
        digitalWrite(DOT_GPIO, LOW);
        break;
      }
      else if (second_count > s_conf_clock.date_interval && display_date) {
        second_count = 0;
        display_date = false;
        displayDate(mon, d);
        return;
      }

      if (!display_date){
        displayDate(mon, d);
        return;
      }
    }
  } while (0);

  if (s_conf_clock.clock_type == e_ClockType_12 && h > 12) {
    h -= 12;
  }

  // 显示时间情况
  if (s_conf_clock.screen == e_SecondScreen_Temp) {
    float ctmp = ds.getTempC();
    ctmp = ds.getTempC() - 1;
    tmp = (int)((ctmp <= 0.4) ? 0 : ctmp);
    digitalWrite(DOT_GPIO, s % 2);

    cur_time = h * 10000 + m * 100 + tmp;
    DigitLed.Display6Num(cur_time);
  } else if (s_conf_clock.screen == e_SecondScreen_Second) {
    tmp = s;
    digitalWrite(DOT_GPIO, LOW);
    cur_time = h * 10000 + m * 100 + tmp;

    DigitLed.Display6Num(cur_time);
  } else {
    digitalWrite(DOT_GPIO, s % 2);
    DigitLed.Display4Num(0, h * 100 + m);
  }
}

void digitalClockDisplay() {
  // digital clock display of the time
  int h = hour();
  int m = minute();
  int s = second();
  int d = day();
  int mon = month();
 
  BELL_OFF;
  brightnessControl();
  usbPosition();
  displayTime(mon, d, h, m, s);

  // 蜂鸣器状态处理
  bell(h, m, s);

}

/*-------- NTP code ----------*/
#define NTP_PACKET_SIZE 48  // NTP time is in the first 48 bytes of message
#define TIMEOUT_LOSS_NET 4
byte packetBuffer[NTP_PACKET_SIZE];  //buffer to hold incoming & outgoing packets

time_t getNtpTime() {
  static int update_cnt = 0;
  int size, i = 0, failed_cnt = 0;
  IPAddress ntpServerIP;  // NTP server's ip address

  Serial.println("Transmit NTP Request");

  // 非初始化状态下，系统时间直接从ds1302芯片里获取
  if (!IsSetupStatus && use_ds1302 && update_cnt < 720 && !update_ds1302) {

    RtcDateTime now_time = Rtc.GetDateTime();
    now_time = Rtc.GetDateTime();

    if (now_time.IsValid()) {
      update_cnt++;
      setTime(now_time.Hour(), now_time.Minute(), now_time.Second(), now_time.Day(), now_time.Month(), now_time.Year());
      return getSysTime();
    }
  }

  if (WiFi.status() == WL_CONNECTED) {
    do {
      failed_cnt++;
      while (Udp.parsePacket() > 0)
        ;  //清空数据
      WiFi.hostByName(ntpServerName[i], ntpServerIP);
      Serial.print(ntpServerName[i++]);
      Serial.print(": ");
      Serial.println(ntpServerIP);
      if (i >= (sizeof(ntpServerName) / sizeof(char*))) i = 0;

      sendNTPpacket(ntpServerIP);
      delay(500);
      size = Udp.parsePacket();

      if (failed_cnt >= (TIMEOUT_LOSS_NET * 2)) {  // 尝试10s仍然从网络获取不了数据则为断网
        break;
      }
    } while (size == 0);

    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      update_ds1302 = false;
      IsSetupStatus = false;
      
      setSyncInterval(SYS_TIME_SYNC_INTERVAL);
      digitalWrite(PIN_LED, HIGH);              // 网络恢复熄灭LED
      
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 = (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];

      if (use_ds1302) {
        /* 更新ds1302时间 */
        RtcDateTime compiled = RtcDateTime(secsSince1900 - 3155673600UL + timeZone * SECS_PER_HOUR);  // 从2000-01-01 开始计时
        Rtc.SetIsWriteProtected(false);
        Rtc.SetDateTime(compiled);
        Rtc.SetIsWriteProtected(true);
        update_cnt = 0;
      }

      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }

  Serial.println("No NTP Response :-(");
  digitalWrite(PIN_LED, LOW);

  if (use_ds1302) {
    RtcDateTime now_time = Rtc.GetDateTime();
    update_cnt = 0;
    now_time = Rtc.GetDateTime();
    if (now_time.IsValid()) {
      setTime(now_time.Hour(), now_time.Minute(), now_time.Second(), now_time.Day(), now_time.Month(), now_time.Year());
      return getSysTime();
    }
  }

  adjustTime(TIMEOUT_LOSS_NET + 1);
  return getSysTime();  // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress& address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;  // LI, Version, Mode
  packetBuffer[1] = 0;           // Stratum, or type of clock
  packetBuffer[2] = 6;           // Polling Interval
  packetBuffer[3] = 0xEC;        // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123);  //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
